﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Diagnostics.CodeAnalysis;
using System.Web.Routing;
using System.Web;
using System.Collections;
using System.Globalization;
using MTO.Framework.Web.Mvc.Resources;

namespace MTO.Framework.Web.Mvc.Html
{
    public static class SelectExtensions
    {
        public static MvcHtmlString GroupDropDownList(this HtmlHelper htmlHelper, string name)
        {
            return SelectInternal(htmlHelper, null /* optionLabel */, name, null /* selectList */, false /* allowMultiple */, null /* htmlAttributes */);
        }

        public static MvcHtmlString GroupDropDownList(this HtmlHelper htmlHelper, string name, string optionLabel)
        {
            return SelectInternal(htmlHelper, optionLabel, name, null/* selectList */, false /* allowMultiple */, null /* htmlAttributes */);
        }

        public static MvcHtmlString GroupDropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<GroupSelectListItem> selectList)
        {
            return GroupDropDownList(htmlHelper, name, selectList, (object)null /* htmlAttributes */);
        }

        public static MvcHtmlString GroupDropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<GroupSelectListItem> selectList, object htmlAttributes)
        {
            return GroupDropDownList(htmlHelper, name, selectList, new RouteValueDictionary(htmlAttributes));
        }

        public static MvcHtmlString GroupDropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<GroupSelectListItem> selectList, IDictionary<string, object> htmlAttributes)
        {
            return SelectInternal(htmlHelper, null /* optionLabel */, name, selectList, false /* allowMultiple */, htmlAttributes);
        }

        public static MvcHtmlString GroupDropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<GroupSelectListItem> selectList, string optionLabel)
        {
            return GroupDropDownList(htmlHelper, name, selectList, optionLabel, null);
        }

        public static MvcHtmlString GroupDropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<GroupSelectListItem> selectList, string optionLabel, object htmlAttributes)
        {
            return GroupDropDownList(htmlHelper, name, selectList, optionLabel, new RouteValueDictionary(htmlAttributes));
        }

        public static MvcHtmlString GroupDropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<GroupSelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
        {
            return SelectInternal(htmlHelper, optionLabel, name, selectList, false /* allowMultiple */, htmlAttributes);
        }

        private static IEnumerable<GroupSelectListItem> GetSelectData(this HtmlHelper htmlHelper, string name)
        {
            object o = null;
            if (htmlHelper.ViewData != null)
            {
                o = htmlHelper.ViewData.Eval(name);
            }
            if (o == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        MvcResources.HtmlHelper_MissingSelectData,
                        name,
                        "IEnumerable<GroupSelectListItem>"));
            }
            IEnumerable<GroupSelectListItem> selectList = o as IEnumerable<GroupSelectListItem>;
            if (selectList == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        MvcResources.HtmlHelper_WrongSelectDataType,
                        name,
                        o.GetType().FullName,
                        "IEnumerable<GroupSelectListItem>"));
            }
            return selectList;
        }

        private static string ListItemToOption(GroupSelectListItem item)
        {
            TagBuilder builder = new TagBuilder("option")
            {
                InnerHtml = HttpUtility.HtmlEncode(item.Text)
            };
            if (item.Value != null)
            {
                builder.Attributes["value"] = item.Value;
            }
            if (item.Selected)
            {
                builder.Attributes["selected"] = "selected";
            }
            return builder.ToString(TagRenderMode.Normal);
        }

        private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, string optionLabel, string name, IEnumerable<GroupSelectListItem> selectList, bool allowMultiple, IDictionary<string, object> htmlAttributes)
        {
            name = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            if (String.IsNullOrEmpty(name))
            {
                throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
            }

            bool usedViewData = false;

            // If we got a null selectList, try to use ViewData to get the list of items.
            if (selectList == null)
            {
                selectList = htmlHelper.GetSelectData(name);
                usedViewData = true;
            }

            object defaultValue = (allowMultiple) ? MvcHelper.GetModelStateValue(name, typeof(string[]), htmlHelper.ViewData) : MvcHelper.GetModelStateValue(name, typeof(string), htmlHelper.ViewData);

            // If we haven't already used ViewData to get the entire list of items then we need to
            // use the ViewData-supplied value before using the parameter-supplied value.
            if (!usedViewData)
            {
                if (defaultValue == null)
                {
                    defaultValue = htmlHelper.ViewData.Eval(name);
                }
            }

            if (defaultValue != null)
            {
                IEnumerable defaultValues = (allowMultiple) ? defaultValue as IEnumerable : new[] { defaultValue };
                IEnumerable<string> values = from object value in defaultValues select Convert.ToString(value, CultureInfo.CurrentCulture);
                HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
                List<GroupSelectListItem> newSelectList = new List<GroupSelectListItem>();

                foreach (GroupSelectListItem item in selectList)
                {
                    item.Selected = (item.Value != null) ? selectedValues.Contains(item.Value) : selectedValues.Contains(item.Text);
                    newSelectList.Add(item);
                }
                selectList = newSelectList;
            }

            // Convert each ListItem to an <option> tag
            StringBuilder listItemBuilder = new StringBuilder();
            var groupListItemBuilders = new Dictionary<string, StringBuilder>();

            // Make optionLabel the first item that gets rendered.
            if (optionLabel != null)
            {
                listItemBuilder.AppendLine(ListItemToOption(new GroupSelectListItem() { Text = optionLabel, Value = String.Empty, Selected = false }));
            }

            //Add all the ones without groups first
            foreach (GroupSelectListItem item in selectList)
            {
                if (String.IsNullOrEmpty(item.Group))
                {
                    listItemBuilder.AppendLine(ListItemToOption(item));
                }
                else
                {
                    if(!groupListItemBuilders.ContainsKey(item.Group))
                    {
                        groupListItemBuilders[item.Group] = new StringBuilder();
                    }
                    groupListItemBuilders[item.Group].AppendLine(ListItemToOption(item));
                }
            }

            if(groupListItemBuilders.Count > 0)
            {
                foreach (var builder in groupListItemBuilders)
                {
                    var optGroupTag = new TagBuilder("optGroup")
                    {
                        InnerHtml = builder.Value.ToString()
                    };

                    optGroupTag.Attributes["label"] = builder.Key;
                    listItemBuilder.AppendLine(optGroupTag.ToString(TagRenderMode.Normal));
                }
            }

            TagBuilder tagBuilder = new TagBuilder("select")
            {
                InnerHtml = listItemBuilder.ToString()
            };
            tagBuilder.MergeAttributes(htmlAttributes);
            tagBuilder.MergeAttribute("name", name, true /* replaceExisting */);
            tagBuilder.GenerateId(name);
            if (allowMultiple)
            {
                tagBuilder.MergeAttribute("multiple", "multiple");
            }

            // If there are any errors for a named field, we add the css attribute.
            ModelState modelState;
            if (htmlHelper.ViewData.ModelState.TryGetValue(name, out modelState))
            {
                if (modelState.Errors.Count > 0)
                {
                    tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
                }
            }

            return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal));
        }
    }
}
